﻿#if TASKSUPPORT
using System.Threading.Tasks;
#endif
using System;
using System.ComponentModel;
using System.Windows.Input;
using JetBrains.Annotations;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkit.ViewModels;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the mediator to provide base logic for closeable view models
    /// </summary>
    public class CloseableViewModelFacade : IDisposable
    {
        #region Fields

        /// <summary>
        ///     Gets the parameter that can be used to immediately close the closeable view model without any check.
        ///     closeableViewModel.Close(WorkspaceViewModel.ImmediateCloseParameter);
        /// </summary>
        public static readonly object ImmediateCloseParameter = new object();

        /// <summary>
        /// Gets the closeable view model.
        /// </summary>
        protected readonly ICloseableViewModel CloseableViewModel;

        /// <summary>
        /// Gets the property changed callback.
        /// </summary>
        protected readonly Action<string> PropertyChangedCallback;

        /// <summary>
        /// Gets the delegate to call closing method in view model.
        /// </summary>
#if TASKSUPPORT
        protected readonly Func<object, Task<bool>> OnClosing;
#else
        protected readonly Func<object, IOperationResult<bool>> OnClosing;
#endif

        /// <summary>
        /// Gets the delegate to call close method in view model.
        /// </summary>
        protected readonly Action<object> OnClosed;

        private static readonly Predicate<object> AlwaysTrue = o => true;
        private IThreadManager _threadManager;
        private ICommand _closeCommand;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="CloseableViewModelFacade" /> class.
        /// </summary>
        private CloseableViewModelFacade(ICloseableViewModel closeableViewModel, Action<string> propertyChangedCallback, Predicate<object> canExecutCloseCommand, [CanBeNull] Action<object> onClosed)
        {
            Should.NotBeNull(closeableViewModel, "closeableViewModel");
            Should.NotBeNull(propertyChangedCallback, "propertyChangedCallback");
            CloseableViewModel = closeableViewModel;
            PropertyChangedCallback = propertyChangedCallback;
            // ReSharper disable once DoNotCallOverridableMethodsInConstructor
            CloseCommand = new RelayCommand(o => CloseableViewModel.Close(o).WithTaskExceptionHandler(closeableViewModel), canExecutCloseCommand ?? AlwaysTrue, CloseableViewModel);
            OnClosed = onClosed;
        }

#if TASKSUPPORT
        /// <summary>
        ///     Initializes a new instance of the <see cref="CloseableViewModelFacade" /> class.
        /// </summary>
        public CloseableViewModelFacade([NotNull]ICloseableViewModel closeableViewModel, [NotNull] Action<string> propertyChangedCallback,[CanBeNull] Predicate<object> canExecutCloseCommand = null,
                                        Func<object, Task<bool>> onClosing = null, Action<object> onClosed = null)
            : this(closeableViewModel, propertyChangedCallback, canExecutCloseCommand, onClosed)
        {
            OnClosing = onClosing;
        }
#else
        /// <summary>
        ///     Initializes a new instance of the <see cref="CloseableViewModelFacade" /> class.
        /// </summary>
        public CloseableViewModelFacade([NotNull]ICloseableViewModel closeableViewModel, [NotNull] Action<string> propertyChangedCallback, [CanBeNull] Predicate<object> canExecutCloseCommand,
                                        Func<object, IOperationResult<bool>> onClosing = null, Action<object> onClosed = null)
            : this(closeableViewModel, propertyChangedCallback, canExecutCloseCommand, onClosed)
        {
            OnClosing = onClosing;
        }
#endif

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets a command that attempts to remove this workspace from the user interface.
        /// </summary>
        public virtual ICommand CloseCommand
        {
            get { return _closeCommand; }
            set
            {
                if (Equals(value, _closeCommand))
                    return;
                _closeCommand = value;
                PropertyChangedCallback("CloseCommand");
            }
        }

        /// <summary>
        ///     Gets or sets the thread manager.
        /// </summary>
        public virtual IThreadManager ThreadManager
        {
            get { return _threadManager; }
            set
            {
                _threadManager = value;
                var relayCommand = CloseCommand as RelayCommand;
                if (relayCommand != null)
                    relayCommand.ThreadManager = value;
            }
        }

        #endregion

        #region Methods

#if TASKSUPPORT
        /// <summary>
        /// Tries to close view-model.
        /// </summary>
        /// <param name="parameter">The specified parameter, if any.</param>
        /// <returns>An instance of task with result.</returns>
        public virtual Task<bool> Close(object parameter)
        {
            try
            {
                CloseableViewModel.NotBeDisposed();
                if (parameter == ImmediateCloseParameter)
                    return CloseInternal(parameter);

                if (OnClosing != null)
                {
                    var closingTask = OnClosing(parameter);
                    return closingTask.ContinueWith(task =>
                                                    {
                                                        if (!task.Result || !OnClosingEvent(parameter))
                                                            return false;
                                                        CloseInternal(parameter);
                                                        return true;
                                                    }, closingTask.GetContinuationOptions());
                }
                if (OnClosingEvent(parameter))
                    return CloseInternal(parameter);
                return MvvmUtils.FalseTaskResult;
            }
            catch (Exception exception)
            {
                var tcs = new TaskCompletionSource<bool>();
                var aggregateException = exception as AggregateException;
                if (aggregateException != null)
                    tcs.SetException(aggregateException.InnerExceptions);
                else
                    tcs.SetException(exception);
                return tcs.Task;
            }
        }

        private Task<bool> CloseInternal(object parameter)
        {
            if (OnClosed != null)
                OnClosed(parameter);
            OnClosedEvent(parameter);
            return MvvmUtils.TrueTaskResult;
        }
#else
        /// <summary>
        ///     Tries to close view-model.
        /// </summary>
        /// <param name="parameter">The specified parameter, if any.</param>
        /// <returns>An instance of operation with result.</returns>
        public virtual IOperationResult<bool> Close(object parameter)
        {
            try
            {
                CloseableViewModel.NotBeDisposed();
                if (parameter == ImmediateCloseParameter)
                    return CloseInternal(parameter);

                if (OnClosing != null)
                {
                    var result = new ManualOperationResult<bool>();
                    var closingResult = OnClosing(parameter);
                    closingResult.BeginWait(operationResult =>
                    {
                        try
                        {
                            if (!operationResult.Result || !OnClosingEvent(parameter))
                            {
                                result.SetResult(false);
                                return;
                            }
                            CloseInternal(parameter);
                            result.SetResult(true);
                        }
                        catch (Exception exception)
                        {
                            result.SetException(exception);
                        }
                    });
                    return result;
                }
                if (!OnClosingEvent(parameter))
                    return MvvmUtils.FalseOperationResult;
                return CloseInternal(parameter);
            }
            catch (Exception exception)
            {
                return OperationResult.FromException<bool>(exception);
            }
        }

        private IOperationResult<bool> CloseInternal(object parameter)
        {
            if (OnClosed != null)
                OnClosed(parameter);
            OnClosedEvent(parameter);
            return MvvmUtils.TrueOperationResult;
        }
#endif

        /// <summary>
        /// Invokes the Closing event.
        /// </summary>
        protected bool OnClosingEvent(object parameter)
        {
            ViewModelClosingDelegate handler = Closing;
            if (handler == null) return true;
            var args = new CancelEventArgs(false);
            handler(CloseableViewModel, args, parameter);
            return !args.Cancel;
        }

        /// <summary>
        /// Invokes the Closed event.
        /// </summary>
        protected void OnClosedEvent(object parameter)
        {
            ViewModelClosedDelegate handler = Closed;
            if (handler != null) handler(CloseableViewModel, parameter);
        }

        #endregion

        #region Events

        /// <summary>
        ///     Called when <see cref="T:MugenMvvmToolkit.Interfaces.ViewModels.ICloseableViewModel" /> removing from the
        ///     workspace.
        /// </summary>
        public virtual event ViewModelClosingDelegate Closing;

        /// <summary>
        ///     Called when <see cref="T:MugenMvvmToolkit.Interfaces.ViewModels.ICloseableViewModel" /> removed from the workspace.
        /// </summary>
        public virtual event ViewModelClosedDelegate Closed;

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
            ViewModelClosingDelegate viewModelClosing = Closing;
            if (viewModelClosing != null)
            {
                foreach (ViewModelClosingDelegate @delegate in viewModelClosing.GetInvocationList())
                    Closing -= @delegate;
            }

            ViewModelClosedDelegate viewModelClosed = Closed;
            if (viewModelClosed != null)
            {
                foreach (ViewModelClosedDelegate @delegate in viewModelClosed.GetInvocationList())
                    Closed -= @delegate;
            }
        }

        #endregion
    }
}